//
// winter - Direct2D GUI framework.
// Copyright (C) 2009 Aleksandar Dezelin 
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#ifndef INTRUSIVE_PTR_H
#define INTRUSIVE_PTR_H

#pragma once
#include "winter_assert.h"
#include <utility>

namespace winter {
namespace services {

template<class T> class intrusive_ptr {
private:
  T* _px;
  typedef intrusive_ptr this_type;

public:
  typedef T element_type;

  intrusive_ptr(): _px(0) {}

  intrusive_ptr(T* p, bool add_ref = true ) : _px(p) {
    if (_px != 0 && add_ref) 
      _px->add_ref();
  }

  intrusive_ptr(intrusive_ptr const& rhs) : _px(rhs._px) {
    if (_px != 0) 
      _px->add_ref();
  }

  template<class U>
  intrusive_ptr(intrusive_ptr<U> const& rhs) : _px(rhs.get()) {
    if (_px != 0)
      _px->add_ref();
  }

  ~intrusive_ptr() {
    if (_px != 0) 
      _px->release();
  }

  template<class U> 
  intrusive_ptr& operator=(intrusive_ptr<U> const& rhs) {
    this_type(rhs).swap(*this);
    return *this;
  }

  intrusive_ptr(intrusive_ptr&& rhs): _px(rhs._px) {
    rhs._px = 0;
  }

  intrusive_ptr& operator=(intrusive_ptr&& rhs) {
    this_type(std::move(rhs)).swap(*this);
    return *this;
  }

  intrusive_ptr& operator=(intrusive_ptr const& rhs) {
    this_type(rhs).swap(*this);
    return *this;
  }

  intrusive_ptr& operator=(T* rhs) {
    this_type(rhs).swap(*this);
    return *this;
  }

  void reset() {
    this_type().swap(*this);
  }

  void reset(T* rhs) {
    this_type(rhs).swap(*this);
  }

  T* get() const {
    return _px;
  }

  T& operator*() const {
    WINTER_ASSERT(_px != 0);
    return *_px;
  }

  T* operator->() const {
    WINTER_ASSERT(_px != 0);
    return _px;
  }

  operator bool() const {
    return _px != 0;
  }

  void swap(intrusive_ptr& rhs) {
    T* tmp = _px;
    _px = rhs._px;
    rhs._px = tmp;
  }
};

template<class T, class U> 
inline bool operator==(intrusive_ptr<T> const& a, intrusive_ptr<U> const& b) {
  return a.get() == b.get();
}

template<class T, class U> 
inline bool operator!=(intrusive_ptr<T> const& a, intrusive_ptr<U> const& b) {
  return a.get() != b.get();
}

template<class T, class U> 
inline bool operator==(intrusive_ptr<T> const& a, U* b) {
  return a.get() == b;
}

template<class T, class U> 
inline bool operator!=(intrusive_ptr<T> const& a, U* b) {
  return a.get() != b;
}

template<class T, class U> 
inline bool operator==(T* a, intrusive_ptr<U> const& b) {
  return a == b.get();
}

template<class T, class U> 
inline bool operator!=(T* a, intrusive_ptr<U> const& b) {
  return a != b.get();
}

template<class T> 
inline bool operator<(intrusive_ptr<T> const& a, intrusive_ptr<T> const& b) {
  return std::less<T*>()(a.get(), b.get());
}

template<class T> 
void swap(intrusive_ptr<T>& lhs, intrusive_ptr<T>& rhs) {
  lhs.swap(rhs);
}

template<class T> 
T* get_pointer(intrusive_ptr<T> const& p) {
  return p.get();
}

template<class T, class U> 
intrusive_ptr<T> static_pointer_cast(intrusive_ptr<U> const& p) {
  return static_cast<T*>(p.get());
}

template<class T, class U> 
intrusive_ptr<T> const_pointer_cast(intrusive_ptr<U> const& p) {
  return const_cast<T*>(p.get());
}

template<class T, class U> 
intrusive_ptr<T> dynamic_pointer_cast(intrusive_ptr<U> const& p) {
  return dynamic_cast<T*>(p.get());
}

class intrusive_ptr_object {
private:
  int _ref;

public:
  intrusive_ptr_object() : _ref(0) {}
  
  ~intrusive_ptr_object() { 
    WINTER_ASSERT(_ref == 0); 
  }

  int add_ref() { 
    return ++_ref; 
  }

  int release() { 
    if (--_ref > 0) 
      return _ref;

    delete this; 
    return 0;
  }
};

}
}

#endif // INTRUSIVE_PTR_H
